import functools
import re


### Exceptions ###

class PropertyNotUnique(Exception):
    "Abstract class."
    
    def __init__(self, value):
        self.value = value
    
    def __str__(self):
        return "Value \"" + self.value + "\" is not unique"

class StringInvalid(Exception):
    "Abstract class."

class StringInvalidLength(StringInvalid):
    "Abstract class."
    
    _min_max_message = "length ({0}) isn't between {1} and {2} characters"
    _min_message = "length ({0}) isn't longer than {1} characters"
    
    # Attribute max_length is optional.
    def __init__(self, string, min_length, max_length=None):
        self.string = string
        self.min_length = min_length
        if (max_length):
            self.max_length = max_length
    
    def __str__(self):
        string_length = len(self.string)
        if self.max_length:
            return self._min_max_message.format(string_length, self.min_length, self.max_length)
        else:
            return self._min_message.format(string_length, self.min_length)

class StringInvalidFormat(StringInvalid):
    "Abstract class."
    
    _message = "\"{0}\" doesn't match regex {1}"
    
    def __init__(self, string, valid_regex):
        self.string = string
        self.valid_regex = valid_regex
    
    def __str__(self):
        return self._message.format(self.string, self.valid_regex)


### Validators ###

def string_length_validator(min_length, max_length, invalid_length_exception):
    """
    Returns a function that given a string value, validates it's length. When value's
    length is smaller than min_length or greater than max_length returned function
    raises invalid_length_exception.
    
    """
    def validate_string_length(string, min_length, max_length, invalid_length_exception):
        if string != None:
            string_length = len(string)
            if string_length < min_length:
                raise invalid_length_exception(string, min_length, max_length)
            if max_length:
                if max_length < string_length:
                    raise invalid_length_exception(string, min_length, max_length)
    
    return functools.partial(
        validate_string_length,
        min_length=min_length,
        max_length=max_length,
        invalid_length_exception=invalid_length_exception
    )
        

def string_format_validator(valid_regex, invalid_format_exception):
    """
    Returns a function that given a string value, validates it's format. When value
    doesn't match valid_regex function raises invalid_format_exception.
    
    """
    def validate_string_format(string, valid_regex, invalid_format_exception):
        if string != None:
            if re.match(valid_regex, string) == None:
                raise invalid_format_exception(string, valid_regex)
    
    return functools.partial(
        validate_string_format,
        valid_regex=valid_regex,
        invalid_format_exception=invalid_format_exception
    )
    


def combine_validators(*validators):
    """
    Accepts any number of validators (validating functions) which it combines into
    one validator. The constructed function when given a value runs each given
    validator with that value.
    
    """
    def run_validators(value, validators_list):
        for validator in validators_list:
            validator(value)
    
    return functools.partial(run_validators, validators_list=[v for v in validators])